package com.local.es.service.impl;

import com.alibaba.fastjson.JSON;
import com.local.es.constants.ESConstants;
import com.local.es.mapper.GoodsMapper;
import com.local.es.pojo.Goods;
import com.local.es.pojo.GoodsESDoc;
import com.local.es.pojo.GoodsSearchParam;
import com.local.es.service.GoodsService;
import com.local.es.utils.PageResultUtil;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;

@Service
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    private RestHighLevelClient esClient;

    @Autowired
    private GoodsMapper goodsMapper;

    @Override
    public List<Goods> getList(Integer isDeleted, Integer page, Integer limit) {
        Integer start = (page - 1) * limit;
        return goodsMapper.getList(isDeleted, start, limit);
    }

    /**
     * 新增
     * @param goods
     * @return
     */
    @Override
    public Integer save(Goods goods) {
        return goodsMapper.save(goods);
    }

    /**
     * 更新
     * @param goods
     * @return
     */
    @Override
    public Integer update(Goods goods) {
        return goodsMapper.update(goods);
    }

    /**
     * 逻辑删除
     * @param goods
     * @return
     */
    @Override
    public Integer delete(Goods goods) {
        return goodsMapper.delete(goods);
    }

    /**
     * 初始化es中goods索引库的文档数据
     * @return
     */
    @Override
    public Integer initESData(Integer page) {
        Integer resultCount = 0;
        try {
            //查询es_goods索引库是否存在，不存在则创建es_goods索引库
            GetIndexRequest getIndexRequest = new GetIndexRequest(ESConstants.ES_GOODS_INDEX);
            boolean exists = this.esClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
            if (!exists) {
                //创建es_goods索引库
                //1.创建request请求
                CreateIndexRequest indexRequest = new CreateIndexRequest(ESConstants.ES_GOODS_INDEX);
                //2.准备请求所需参数：DSL语句
                indexRequest.source(ESConstants.ES_GOODS_INDEX_TEMPLATE, XContentType.JSON);
                //3.发起请求
                this.esClient.indices().create(indexRequest, RequestOptions.DEFAULT);
            }

            //获取商品列表
            Integer limit = 50;
            Integer start = (page - 1) * limit;
            List<Goods> goodsList = goodsMapper.getList(0, start, limit);
            resultCount = goodsList.size();

            //将商品批量添加到es_goods文档
            //1.创建批量添加请求
            BulkRequest bulkRequest = new BulkRequest();
            for (Goods goods : goodsList) {
                //2.准备批量添加所需参数
                GoodsESDoc goodsESDoc = new GoodsESDoc(goods);
                bulkRequest.add(new IndexRequest(ESConstants.ES_GOODS_INDEX)
                        .id(goodsESDoc.getGoodsId().toString())
                        .source(JSON.toJSONString(goodsESDoc), XContentType.JSON));
            }
            //3.发起批量添加请求
            this.esClient.bulk(bulkRequest, RequestOptions.DEFAULT);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return resultCount;
    }


    /**
     * es搜索
     *
     * @param param
     * @return
     */
    @Override
    public PageResultUtil<GoodsESDoc> searchES(GoodsSearchParam param) {
        try {
            //1.创建请求request
            SearchRequest searchRequest = new SearchRequest(ESConstants.ES_GOODS_INDEX);

            //2.构建请求参数
            this.buildBasicQuery(param, searchRequest);

            //2.1分页
            Integer page = param.getPage();
            Integer limit = param.getLimit();
            page = (Objects.nonNull(page) && page >= 1) ? page : 1;
            limit = (Objects.nonNull(limit) && limit >= 1) ? limit : 10;
            Integer start = (page - 1) * limit;
            searchRequest.source().from(start).size(limit);

            //2.2排序：默认按es的_score desc排序，即es默认的算分规则降序
            String sort = param.getSort();
            String order = param.getOrder();
            sort = Objects.isNull(sort) || sort.isEmpty() ? "_score" : sort;
            order = Objects.isNull(order) || order.isEmpty() ? "desc" : order;
            searchRequest.source().sort(sort, SortOrder.fromString(order));

            //2.3搜索结果找中，关键词高亮
            searchRequest.source().highlighter(new HighlightBuilder().field("goodsName").requireFieldMatch(false).field("intro").requireFieldMatch(false));

            //3.发起请求
            SearchResponse searchResponse = this.esClient.search(searchRequest, RequestOptions.DEFAULT);

            //4.处理、返回结果
            return this.handleResponse(searchResponse);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 处理、返回搜索结果
     * @param searchResponse
     * @return
     */
    private PageResultUtil<GoodsESDoc> handleResponse(SearchResponse searchResponse) {
        //1.解析结果
        SearchHits hits = searchResponse.getHits();

        //2.结果总数
        TotalHits totalHits = hits.getTotalHits();

        //3.获取结果列表
        SearchHit[] hitsArr = hits.getHits();
        List<GoodsESDoc> list = new ArrayList<>();
        for (SearchHit documentFields : hitsArr) {
            //获取结果中的_source
            String source = documentFields.getSourceAsString();
            //将source转化为Object对象
            GoodsESDoc goodsESDoc = JSON.parseObject(source, GoodsESDoc.class);

            //获取结果中的高亮字段
            Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
            if (!CollectionUtils.isEmpty(highlightFields)) {
                HighlightField highlightFieldGoodsName = highlightFields.get("goodsName");
                if (Objects.nonNull(highlightFieldGoodsName)) {
                    String goodsNameHighlight = highlightFieldGoodsName.getFragments()[0].toString();
                    goodsESDoc.setGoodsName(goodsNameHighlight);
                }

                HighlightField highlightFieldIntro = highlightFields.get("intro");
                if (Objects.nonNull(highlightFieldIntro)) {
                    String introHighlight = highlightFieldIntro.getFragments()[0].toString();
                    goodsESDoc.setIntro(introHighlight);
                }
            }

            list.add(goodsESDoc);
        }

        return new PageResultUtil<GoodsESDoc>( (int) totalHits.value, 1, 1, list);
    }


    /**
     * 根据搜索条件，构建请求参数
     * @param param
     * @param searchRequest
     */
    private void buildBasicQuery(GoodsSearchParam param, SearchRequest searchRequest) {
        //构建参数及查询条件
        BoolQueryBuilder boolQueryBuilders = QueryBuilders.boolQuery();

        //关键词搜索
        String keyword = param.getKeyword();
        if (Objects.nonNull(keyword) && keyword.length() > 0) {
            //搜索关键词不为空
            boolQueryBuilders.must(QueryBuilders.matchQuery("all", keyword));
        } else {
            //搜索关键词为空
            boolQueryBuilders.must(QueryBuilders.matchAllQuery());
        }

        //价格区间搜索
        Integer minPrice = param.getMinPrice();
        Integer maxPrice = param.getMaxPrice();
        if (Objects.nonNull(minPrice) && minPrice > 0) {
            boolQueryBuilders.filter(QueryBuilders.rangeQuery("salePrice").gte(minPrice));
        }
        if (Objects.nonNull(maxPrice) && maxPrice > 0) {
            boolQueryBuilders.filter(QueryBuilders.rangeQuery("salePrice").lte(maxPrice));
        }

        /*
        //算分控制，人为干预搜索结果的排序：isAD为true，排在前面
        FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
                boolQueryBuilders, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                QueryBuilders.termQuery("isAD", true),
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }
        );
         */

        searchRequest.source().query(boolQueryBuilders);
    }


    /**
     * 通过拼音、字母的关键词得到搜索建议提示词，如：输入“xm”得到“小米”的提示
     * @param keyword
     * @return
     */
    @Override
    public List<String> searchSuggestion(String keyword) {
        try {
            //1.创建请求
            SearchRequest request = new SearchRequest(ESConstants.ES_GOODS_INDEX);

            //2.准备DSL：搜索的自动补全
            request.source().suggest(new SuggestBuilder().addSuggestion(
                    "suggestions", //搜索名称，后面解析结果的时候要用到，根据名称来获取结果
                    SuggestBuilders.completionSuggestion("suggestion") //自动补全的字段
                            .prefix(keyword) //搜索词
                            .skipDuplicates(true) //过滤重复的数据
                            .size(10) //返回结果数
            ));

            //3.发起请求
            SearchResponse response = this.esClient.search(request, RequestOptions.DEFAULT);

            //4.结果解析
            Suggest suggest = response.getSuggest();
            //4.1根据查询的名称，获取结果
            CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
            //获取options
            List<CompletionSuggestion.Entry.Option> suggestionsOptions = suggestions.getOptions();

            //循环options，获取自动补全的内容
            List<String> suggestionList = new ArrayList<>();
            for (CompletionSuggestion.Entry.Option suggestionsOption : suggestionsOptions) {
                String suggestionText = suggestionsOption.getText().toString();
                suggestionList.add(suggestionText);
            }

            return suggestionList;

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 根据goodsId更新es
     * @param goodsId
     * @return
     */
    @Override
    public Integer updateES(Integer goodsId) {
        try {
            Goods goods = goodsMapper.getById(goodsId);
            GoodsESDoc goodsESDoc = new GoodsESDoc(goods);

            //1.创建请求
            IndexRequest request = new IndexRequest(ESConstants.ES_GOODS_INDEX).id(goodsId.toString());
            //2.构建请求参数
            request.source(JSON.toJSONString(goodsESDoc), XContentType.JSON);
            //3.发起请求
            this.esClient.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return goodsId;
    }


    /**
     * 根据goodsId删除es
     * @param goodsId
     * @return
     */
    @Override
    public Integer deleteES(Integer goodsId) {
        try {
            DeleteRequest request = new DeleteRequest(ESConstants.ES_GOODS_INDEX, goodsId.toString());
            this.esClient.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return goodsId;
    }
}
